// Magic Software, Inc.
// http://www.magic-software.com
// Copyright (c) 2000, All Rights Reserved
//
// Source code from Magic Software is supplied under the terms of a license
// agreement and may not be copied or disclosed except in accordance with the
// terms of that agreement.  The various license agreements may be found at
// the Magic Software web site.  This file is subject to the license
//
// RESTRICTED USE SOURCE CODE
// http://www.magic-software.com/License/restricted.pdf

#include "MgcMultipleCurve3.h"
#include "MgcTriMesh.h"
#include "MgcTubeSurface.h"

MgcImplementRTTI(MgcTubeSurface,MgcTriMesh);
MgcImplementStream(MgcTubeSurface);

//----------------------------------------------------------------------------
MgcTubeSurface::MgcTubeSurface (MgcMultipleCurve3* pkMedial,
    RadialFunction oRadial, bool bClosed, unsigned int uiMedialSamples,
    unsigned int uiSliceSamples, bool bSampleByArcLength, bool bWantNormals,
    const MgcVector3& rkUpVector, const MgcVector2& rkTextureMin,
    const MgcVector2& rkTextureMax)
{
    assert( pkMedial && oRadial );

    m_pkMedial = pkMedial;
    m_oRadial = oRadial;
    m_bClosed = bClosed;

    // default tessellation
    Tessellate(uiMedialSamples,uiSliceSamples,bSampleByArcLength,bWantNormals,
        rkUpVector,rkTextureMin,rkTextureMax);
}
//----------------------------------------------------------------------------
MgcTubeSurface::MgcTubeSurface ()
{
    m_pkMedial = 0;
    m_oRadial = 0;
    m_bClosed = false;
}
//----------------------------------------------------------------------------
MgcTubeSurface::~MgcTubeSurface ()
{
    delete m_pkMedial;
}
//----------------------------------------------------------------------------
unsigned int MgcTubeSurface::Index (unsigned short uiS, unsigned short uiM)
{
    return uiS + (m_uiSliceSamples+1)*uiM;
}
//----------------------------------------------------------------------------
void MgcTubeSurface::ComputeVertices (unsigned int& ruiVertexQuantity,
    MgcVector3*& rakVertex)
{
    if ( m_bClosed )
        ruiVertexQuantity = (m_uiSliceSamples+1)*(m_uiMedialSamples+1);
    else
        ruiVertexQuantity = (m_uiSliceSamples+1)*m_uiMedialSamples;

    rakVertex = new MgcVector3[ruiVertexQuantity];

    // Compute slice vertex coefficients.  The first and last coefficients
    // are duplicated to allow a closed cross section that has two different
    // pairs of texture coordinates at the shared vertex.
    MgcReal* afSin = new MgcReal[m_uiSliceSamples + 1];
    MgcReal* afCos = new MgcReal[m_uiSliceSamples + 1];
    unsigned int uiS;
    for (uiS = 0; uiS < m_uiSliceSamples; uiS++)
    {
        MgcReal fAngle = uiS*MgcMath::TWO_PI/m_uiSliceSamples;
        afCos[uiS] = MgcMath::Cos(fAngle);
        afSin[uiS] = MgcMath::Sin(fAngle);
    }
    afSin[m_uiSliceSamples] = afSin[0];
    afCos[m_uiSliceSamples] = afCos[0];

    // compute vertices
    MgcReal fTMin = m_pkMedial->GetMinTime();
    MgcReal fTRange = m_pkMedial->GetMaxTime() - fTMin;
    MgcReal fTotalLength;
    if ( m_bClosed )
        fTotalLength = m_pkMedial->GetTotalLength();
    else
        fTotalLength = 0.0;

    for (unsigned int uiM = 0, uiV = 0; uiM < m_uiMedialSamples; uiM++)
    {
        MgcReal fT;
        if ( m_bClosed )
        {
            if ( m_bSampleByArcLength )
            {
                fT = m_pkMedial->GetTime(
                    uiM*fTotalLength/MgcReal(m_uiMedialSamples));
            }
            else
            {
                fT = fTMin + uiM*fTRange/MgcReal(m_uiMedialSamples);
            }
        }
        else
        {
            if ( m_bSampleByArcLength )
            {
                fT = m_pkMedial->GetTime(
                    uiM*fTotalLength/MgcReal(m_uiMedialSamples-1));
            }
            else
            {
                fT = fTMin + uiM*fTRange/MgcReal(m_uiMedialSamples-1);
            }
        }

        MgcReal fRadius = m_oRadial(fT);

        // compute frame (position P, tangent T, normal N, binormal B)
        MgcVector3 kP, kT, kN, kB;
        if ( m_kUpVector != MgcVector3::ZERO )
        {
            // Always use 'up' vector N rather than curve normal.  You must
            // constrain the curve so that T and N are never parallel.  To
            // build the frame from this, let
            //     B = Cross(T,N)/Length(Cross(T,N))
            // and replace
            //     N = Cross(B,T)/Length(Cross(B,T)).
            kP = m_pkMedial->GetPosition(fT);
            kT = m_pkMedial->GetTangent(fT);
            kB = kT.UnitCross(MgcVector3::UNIT_Z);
            kN = kB.UnitCross(kT);
        }
        else
        {
            // use Frenet frame to create slices
            m_pkMedial->GetFrame(fT,kP,kT,kN,kB);
        }

        // compute slice vertices, duplication at end point as noted earlier
        unsigned int uiSave = uiV;
        for (uiS = 0; uiS < m_uiSliceSamples; uiS++)
        {
            rakVertex[uiV] = kP + fRadius*(afCos[uiS]*kN + afSin[uiS]*kB);
            uiV++;
        }
        rakVertex[uiV] = rakVertex[uiSave];
        uiV++;
    }

    if ( m_bClosed )
    {
        for (uiS = 0; uiS <= m_uiSliceSamples; uiS++)
        {
            unsigned int uiI1 = Index(uiS,m_uiMedialSamples);
            unsigned int uiI0 = Index(uiS,0);
            rakVertex[uiI1] = rakVertex[uiI0];
        }
    }

    delete[] afSin;
    delete[] afCos;
}
//----------------------------------------------------------------------------
MgcVector3* MgcTubeSurface::ComputeNormals (unsigned int uiVertexQuantity,
    MgcVector3* akVertex)
{
    MgcVector3* akNormal = new MgcVector3[uiVertexQuantity];

    unsigned int uiSm, uiSp, uiMm, uiMp;
    MgcVector3 kDir0, kDir1;
    unsigned int uiS;

    // interior normals (central differences)
    for (unsigned int uiM = 1; uiM <= m_uiMedialSamples-2; uiM++)
    {
        for (uiS = 0; uiS < m_uiSliceSamples; uiS++)
        {
            uiSm = ( uiS > 0 ? uiS-1 : m_uiSliceSamples-1 );
            uiSp = uiS + 1;
            uiMm = uiM - 1;
            uiMp = uiM + 1;
            kDir0 = akVertex[Index(uiSm,uiM)] - akVertex[Index(uiSp,uiM)];
            kDir1 = akVertex[Index(uiS,uiMm)] - akVertex[Index(uiS,uiMp)];
            akNormal[Index(uiS,uiM)] = kDir0.UnitCross(kDir1);
        }
        akNormal[Index(m_uiSliceSamples,uiM)] = akNormal[Index(0,uiM)];
    }

    // boundary normals
    if ( m_bClosed )
    {
        // central differences
        for (uiS = 0; uiS < m_uiSliceSamples; uiS++)
        {
            uiSm = ( uiS > 0 ? uiS-1 : m_uiSliceSamples-1 );
            uiSp = uiS + 1;

            // m = 0
            kDir0 = akVertex[Index(uiSm,0)] - akVertex[Index(uiSp,0)];
            kDir1 = akVertex[Index(uiS,m_uiMedialSamples-1)] -
                akVertex[Index(uiS,1)];
            akNormal[uiS] = kDir0.UnitCross(kDir1);

            // m = max
            akNormal[Index(uiS,m_uiMedialSamples)] = akNormal[Index(uiS,0)];
        }
        akNormal[Index(m_uiSliceSamples,0)] = akNormal[Index(0,0)];
        akNormal[Index(m_uiSliceSamples,m_uiMedialSamples)] =
            akNormal[Index(0,m_uiMedialSamples)];
    }
    else
    {
        // one-sided finite differences

        // m = 0
        for (uiS = 0; uiS < m_uiSliceSamples; uiS++)
        {
            uiSm = ( uiS > 0 ? uiS-1 : m_uiSliceSamples-1 );
            uiSp = uiS + 1;
            kDir0 = akVertex[Index(uiSm,0)] - akVertex[Index(uiSp,0)];
            kDir1 = akVertex[Index(uiS,0)] - akVertex[Index(uiS,1)];
            akNormal[Index(uiS,0)] = kDir0.UnitCross(kDir1);
        }
        akNormal[Index(m_uiSliceSamples,0)] = akNormal[Index(0,0)];

        // m = max-1
        for (uiS = 0; uiS < m_uiSliceSamples; uiS++)
        {
            uiSm = ( uiS > 0 ? uiS-1 : m_uiSliceSamples-1 );
            uiSp = uiS + 1;
            kDir0 = akVertex[Index(uiSm,m_uiMedialSamples-1)] -
                akVertex[Index(uiSp,m_uiMedialSamples-1)];
            kDir1 = akVertex[Index(uiS,m_uiMedialSamples-2)] -
                akVertex[Index(uiS,m_uiMedialSamples-1)];
            akNormal[uiS] = kDir0.UnitCross(kDir1);
        }
        akNormal[Index(m_uiSliceSamples,m_uiMedialSamples-1)] =
            akNormal[Index(0,m_uiMedialSamples-1)];
    }

    return akNormal;
}
//----------------------------------------------------------------------------
MgcVector2* MgcTubeSurface::ComputeTextures (unsigned int uiVertexQuantity)
{
    MgcVector2* akTexture = new MgcVector2[uiVertexQuantity];

    MgcVector2 kTextureRange = m_kTextureMax - m_kTextureMin;

    unsigned int uiMMax;
    if ( m_bClosed )
        uiMMax = m_uiMedialSamples;
    else
        uiMMax = m_uiMedialSamples - 1;

    for (unsigned int uiM = 0, uiV = 0; uiM <= uiMMax; uiM++)
    {
        MgcReal fMRatio = MgcReal(uiM)/MgcReal(uiMMax);
        MgcReal fMValue = m_kTextureMin.y + fMRatio*kTextureRange.y;
        for (unsigned int uiS = 0; uiS <= m_uiSliceSamples; uiS++)
        {
            MgcReal fSRatio = MgcReal(uiS)/MgcReal(m_uiSliceSamples);
            MgcReal fSValue = m_kTextureMin.x + fSRatio*kTextureRange.x;
            akTexture[uiV].x = fSValue;
            akTexture[uiV].y = fMValue;
            uiV++;
        }
    }

    return akTexture;
}
//----------------------------------------------------------------------------
void MgcTubeSurface::ComputeConnectivity (unsigned int& ruiTriangleQuantity,
    unsigned int*& rauiConnect)
{
    ruiTriangleQuantity = 2*m_uiSliceSamples*m_uiMedialSamples;
    rauiConnect = new unsigned int[3*ruiTriangleQuantity];

    unsigned int* puiConnect = rauiConnect;
    for (unsigned int uiM = 0, uiMStart = 0; uiM < m_uiMedialSamples; uiM++)
    {
        unsigned int uiI0 = uiMStart;
        unsigned int uiI1 = uiI0 + 1;
        uiMStart += m_uiSliceSamples + 1;
        unsigned int uiI2 = uiMStart;
        unsigned int uiI3 = uiI2 + 1;
        for (unsigned int uiS = 0; uiS < m_uiSliceSamples; uiS++)
        {
            // TO DO.  The ordering of the vertices assumes you want to view
            // the tube from the inside.  If you want a view from the outside
            // or if you want a double-sided tube, this function must change.
            //
            // WARNING.  Under Visual C++ 6.0, service pack 2, release build
            // optimization set to 'maximize speed', the optimizer incorrectly
            // compiles the following block
            //   *puiConnect++ = uiI0;
            //   *puiConnect++ = uiI2;
            //   *puiConnect++ = uiI1;
            //   *puiConnect++ = uiI1;
            //   *puiConnect++ = uiI2;
            //   *puiConnect++ = uiI3;
            //   uiI0++;
            //   uiI1++;
            //   uiI2++;
            //   uiI3++;
            // That is why I have the statements reordered.  The source+asm
            // listing showed that part of the uiM-loop was being compiled
            // twice, really strange...

            *puiConnect++ = uiI0;
            uiI0++;
            *puiConnect++ = uiI2;
            *puiConnect++ = uiI1;
            *puiConnect++ = uiI1;
            uiI1++;
            *puiConnect++ = uiI2;
            uiI2++;
            *puiConnect++ = uiI3;
            uiI3++;
        }
    }
}
//----------------------------------------------------------------------------
void MgcTubeSurface::Tessellate (unsigned int uiMedialSamples,
    unsigned int uiSliceSamples, bool bSampleByArcLength, bool bWantNormals,
    const MgcVector3& rkUpVector, const MgcVector2& rkTextureMin,
    const MgcVector2& rkTextureMax)
{
    m_uiMedialSamples = uiMedialSamples;
    m_uiSliceSamples = uiSliceSamples;
    m_bSampleByArcLength = bSampleByArcLength;
    m_bWantNormals = bWantNormals;
    m_kUpVector = rkUpVector;
    m_kTextureMin = rkTextureMin;
    m_kTextureMax = rkTextureMax;

    unsigned int uiVertexQuantity;
    MgcVector3* akVertex;
    ComputeVertices(uiVertexQuantity,akVertex);

    MgcVector3* akNormal;
    if ( m_bWantNormals )
        akNormal = ComputeNormals(uiVertexQuantity,akVertex);
    else
        akNormal = 0;

    MgcVector2* akTexture = ComputeTextures(uiVertexQuantity);

    unsigned int uiTriangleQuantity;
    unsigned int* auiConnect;
    ComputeConnectivity(uiTriangleQuantity,auiConnect);

    // create the triangle mesh for the tube surface
    Reconstruct(uiVertexQuantity,akVertex,akNormal,0,akTexture,
        uiTriangleQuantity,auiConnect);

    UpdateModelBound();
}
//----------------------------------------------------------------------------

//---------------------------------------------------------------------------
// streaming
//---------------------------------------------------------------------------
MgcObject* MgcTubeSurface::Factory (MgcStream& rkStream)
{
    MgcTubeSurface* pkObject = new MgcTubeSurface;
    MgcStream::Link* pkLink = new MgcStream::Link(pkObject);
    pkObject->Load(rkStream,pkLink);
    return pkObject;
}
//---------------------------------------------------------------------------
void MgcTubeSurface::Load (MgcStream& rkStream, MgcStream::Link* pkLink)
{
    MgcTriMesh::Load(rkStream,pkLink);

    // native data
    MgcStreamRead(rkStream,m_bClosed);
    MgcStreamRead(rkStream,m_uiMedialSamples);
    MgcStreamRead(rkStream,m_uiSliceSamples);
    MgcStreamRead(rkStream,m_bSampleByArcLength);
    MgcStreamRead(rkStream,m_bWantNormals);
    MgcStreamRead(rkStream,m_kUpVector);
    MgcStreamRead(rkStream,m_kTextureMin);
    MgcStreamRead(rkStream,m_kTextureMax);

    // TO DO.  See note in MgcTubeSurface::Save.
    m_pkMedial = 0;
    m_oRadial = 0;
}
//---------------------------------------------------------------------------
void MgcTubeSurface::Link (MgcStream& rkStream, MgcStream::Link* pkLink)
{
    MgcTriMesh::Link(rkStream,pkLink);
}
//---------------------------------------------------------------------------
bool MgcTubeSurface::Register (MgcStream& rkStream)
{
    return MgcTriMesh::Register(rkStream);
}
//---------------------------------------------------------------------------
void MgcTubeSurface::Save (MgcStream& rkStream)
{
    MgcTriMesh::Save(rkStream);

    // native data
    MgcStreamWrite(rkStream,m_bClosed);
    MgcStreamWrite(rkStream,m_uiMedialSamples);
    MgcStreamWrite(rkStream,m_uiSliceSamples);
    MgcStreamWrite(rkStream,m_bSampleByArcLength);
    MgcStreamWrite(rkStream,m_bWantNormals);
    MgcStreamWrite(rkStream,m_kUpVector);
    MgcStreamWrite(rkStream,m_kTextureMin);
    MgcStreamWrite(rkStream,m_kTextureMax);

    // TO DO.  The class MgcMultipleCurve3 is abstract and does not know
    // about the data representation for the derived-class object that is
    // passed to the MgcTubeSurface constructor.  RadialFunction is a type of
    // function.  Saving the function pointer is useless since on loading,
    // there is no current way to 'link' to the correct function pointer.
    // Because of this, any loaded MgcTubeSurface object will require the
    // application to manually set the curve and function via the Medial()
    // and Radial() members.
    //
    // Streaming support should probably be added to the curve classes and
    // a mechanism for saving function pointers should probably be created.
}
//---------------------------------------------------------------------------
